#include "common.h"
#include "counter.h"
#include "Vrob.h"
#include "Vrob___024root.h"
#define TRACE 1
#define MAX_WAVE 1000000
#define MAX_CYCLE 1000000
Vrob *top;
VerilatedFstC *tfp;
static uint64_t wave_cnt = 0;
#define WRITE_WIDTH 2
#define ISSUE_WIDTH 2
#define COMMIT_WIDTH 2
#define ROB_SIZE 92
Counter cycle("cycle");

bool percent(int change)
{
    uint32_t r = rand();
    r %= 100;
    return r < change;
}
uint32_t rand_n(uint32_t n)
{
    uint32_t r = rand();
    return r % n;
}
void dump()
{
#if TRACE
    if (wave_cnt < MAX_WAVE)
    {
        tfp->dump(Verilated::time());
        wave_cnt++;
    }
#endif
}
void run()
{
    top->eval();
    dump();
    Verilated::timeInc(1);

    top->clk = 0;
    top->eval();
    dump();
    Verilated::timeInc(2);

    top->clk = 1;
    top->eval();
    dump();
    Verilated::timeInc(1);
}
void clear_all()
{
    for (size_t i = 0; i < ISSUE_WIDTH; i++)
    {
        top->i_issue_valid[i] = 0;
        top->i_finish_valid[i] = 0;
        top->i_exeception_valid[i] = 0;
    }
    for (size_t i = 0; i < WRITE_WIDTH; i++)
    {
        top->i_write_op_valid[i] = 0;
    }
    top->i_write_valid = 0;
    top->o_commit_ready = 0;
}
void reset()
{
    clear_all();

    top->rst = 1;
    run();
    run();
    run();
    top->rst = 0;
}
void make_rand_input_full()
{
    // LOG("make_rand_input_full");
    for (size_t i = 0; i < WRITE_WIDTH; i++)
    {
        top->i_write_op_valid[i] = 1;
    }
    top->i_write_valid = 1;
}
void make_rand_input()
{
    // LOG("make_rand_input");
    uint32_t r = rand();
    r %= (WRITE_WIDTH + 1);
    for (size_t i = 0; i < r; i++)
    {
        top->i_write_op_valid[i] = 1;
    }
    top->i_write_valid = 1;
}
void clear_all_fired()
{

    for (size_t i = 0; i < ISSUE_WIDTH; i++)
    {
        top->i_issue_valid[i] = 0;
        top->i_finish_valid[i] = 0;
        top->i_exeception_valid[i] = 0;
    }
    if (top->i_write_ready & top->i_write_valid)
    {
        for (size_t i = 0; i < WRITE_WIDTH; i++)
        {
            top->i_write_op_valid[i] = 0;
        }
        top->i_write_valid = 0;
    }
    if (top->o_commit_valid & top->o_commit_ready)
    {
        top->o_commit_ready = 0;
    }
    top->eval();
}
#include <queue>
typedef struct
{
    int ptr;
    bool finish;
} ROB_Entry;
void rob_size_test()
{
    LOG("rob_max_size_test");
    std::queue<ROB_Entry> rob;
    int max_size = 0;
    // LOG("Step1");
    do
    {
        if (top->i_write_ready & top->i_write_valid)
        {
            for (size_t i = 0; i < WRITE_WIDTH; i++)
            {
                if (top->i_write_op_valid[i])
                {
                    max_size++;

                    rob.push({top->i_write_rob_idx[i]});
                }
            }
        }
        run();
        cycle++;
        clear_all_fired();
        if (!top->i_write_ready)
            break;
        if (!top->i_write_valid && percent(50))
        {
            make_rand_input_full();
        }
    } while ((!Verilated::gotFinish()) && cycle < MAX_CYCLE);
    Assert(max_size == ROB_SIZE, "ROB max size %d not equal %d", max_size, ROB_SIZE);
    clear_all();
    // LOG("Step2");
    // Modify state to issue/finish
    do
    {
        run();
        cycle++;
        clear_all_fired();
        if (rob.empty())
            break;
        uint32_t issue_num = rand_n(ISSUE_WIDTH + 1);
        for (size_t i = 0; i < issue_num; i++)
        {
            if (rob.empty())
                break;
            top->i_finish_valid[i] = 1;
            top->i_finish_rob_idx[i] = rob.front().ptr;
            top->i_issue_valid[i] = 1;
            top->i_issue_rob_idx[i] = rob.front().ptr;
            rob.pop();
        }
    } while ((!Verilated::gotFinish()) && cycle < MAX_CYCLE);
    // LOG("Step3");
    clear_all();
    int pop_size = 0;
    do
    {
        if (top->o_commit_valid & top->o_commit_ready)
        {
            for (size_t i = 0; i < WRITE_WIDTH; i++)
            {
                if (top->o_commit_op_valid[i])
                {
                    pop_size++;
                }
            }
        }
        run();
        cycle++;
        clear_all_fired();
        if (!top->o_commit_valid)
            break;
        top->o_commit_ready = percent(40);
    } while ((!Verilated::gotFinish()) && cycle < MAX_CYCLE);
    Assert(max_size == pop_size, "ROB pop size %d not equal %d", pop_size, max_size);
    clear_all();
}
#include <list>
void rob_test()
{
    LOG("rob_test");

    std::vector<ROB_Entry> rob;
    std::vector<int> issue_list, finish_list, exeception_list;

    reset();
    do
    {
        top->eval();
        if (top->i_write_ready & top->i_write_valid)
        {
            for (size_t i = 0; i < WRITE_WIDTH; i++)
            {
                if (top->i_write_op_valid[i])
                {
                    rob.push_back({top->i_write_rob_idx[i]});
                    issue_list.push_back({top->i_write_rob_idx[i]});
                    // exeception_list.push_back({top->i_write_rob_idx[i]});
                }
            }
        }
        if (top->o_commit_valid & top->o_commit_ready)
        {
            for (size_t i = 0; i < COMMIT_WIDTH; i++)
            {
                if (top->o_commit_op_valid[i])
                {
                    // LOG("Commit %d", top->rootp->rob__DOT__commit_rob_idx[i]);
                }
            }
        }
        if (top->o_redirect_valid)
        {
            rob.clear();
            issue_list.clear();
            finish_list.clear();
            LOG("Redirect");
            // LOG("Redirect %d", top->rootp->rob__DOT__mux_redirect_ptr__DOT__out);
        }
        run();
        cycle++;
        clear_all_fired();
        if (!top->i_write_valid && percent(50))
        {
            make_rand_input();
        }
        if (!top->o_commit_ready && percent(80))
        {
            top->o_commit_ready = 1;
        }
        if (percent(80))
        {
            uint32_t issue_num = rand_n(ISSUE_WIDTH + 1);
            for (size_t i = 0; i < issue_num; i++)
            {
                if (issue_list.empty())
                    break;
                uint32_t rand_op = rand_n(issue_list.size());
                top->i_issue_valid[i] = 1;
                top->i_issue_rob_idx[i] = issue_list[rand_op];
                finish_list.push_back(issue_list[rand_op]);
                // LOG("Issue %d", issue_list[rand_op]);
                issue_list.erase(issue_list.begin() + rand_op);
            }
        }
        if (percent(70))
        {
            uint32_t finish_num = rand_n(ISSUE_WIDTH + 1);
            for (size_t i = 0; i < finish_num; i++)
            {
                if (finish_list.empty())
                    break;
                uint32_t rand_op = rand_n(finish_list.size());
                top->i_finish_valid[i] = 1;
                top->i_finish_rob_idx[i] = finish_list[rand_op];
                if (percent(5))
                {
                    uint32_t exeception_code = rand_n(64);
                    top->i_exeception_valid[i] = 1;
                    top->i_exeception_rob_idx[i] = finish_list[rand_op];
                    top->i_exeception_code[i] = exeception_code;
                }
                // LOG("Finish %d", finish_list[rand_op]);
                finish_list.erase(finish_list.begin() + rand_op);
            }
        }
    } while ((!Verilated::gotFinish()) && cycle < MAX_CYCLE);
}

int main()
{
    srand(0);
    Verilated::randSeed(0);
    Verilated::randReset(1);
    top = new Vrob();
#if TRACE
    Verilated::traceEverOn(true);
    tfp = new VerilatedFstC();
    top->trace(tfp, 0);
    tfp->open("Vrob.fst");
#endif
    reset();
    // Run all tests
    for (size_t i = 0; i < 100; i++)
    {
        rob_size_test();
    }
    rob_test();
    // Final model cleanup
    run();
    top->final();
#if TRACE
    tfp->close();
    delete tfp;
#endif
    delete top;
    Counter::print_counters();
    LOG("All test done");
    return 0;
}